package com.imm.net.http.base

import java.io.IOException
import java.net.InetAddress
import java.net.Socket
import java.security.GeneralSecurityException
import java.util.LinkedList
import java.util.Locale
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLSocket
import javax.net.ssl.SSLSocketFactory
import javax.net.ssl.X509TrustManager

/**
 * <p>------------------------------------------------------
 * <p> Copyright (C) 2021 zhangmz, All rights reserved.
 * <p> If there is technical discussion, you can contact zhangmz90@foxmail.com
 * <p>------------------------------------------------------
 * <p> android 5.0 以下 低配版本支持TLS1.1,1.2协议
 * <p> 参考：https://blog.csdn.net/webber888/article/details/80993471
 * <p> https://developer.android.com/about/versions/android-5.0-changes.html#ssl
 *
 * @author Created by zhangmz
 * @date on 2022/12/28
 */
class SSLSocketFactoryCompat(x509Tm: X509TrustManager?) : SSLSocketFactory() {

    private var defaultFactory: SSLSocketFactory? = null
    private var protocolsCompat: Array<String>? = null
    private var cipherSuitesCompat: Array<String>? = null

    init {
        try {
            (getDefault().createSocket() as? SSLSocket)?.let { socket ->
                /* set reasonable protocol versions */
                // - enable all supported protocols (enables TLSv1.1 and TLSv1.2 on Android <5.0)
                // - remove all SSL versions (especially SSLv3) because they're insecure now
                val protocols = LinkedList<String?>()
                for (i in socket.supportedProtocols.indices) {
                    val protocol: String? = socket.supportedProtocols[i]
                    if (protocol?.uppercase(Locale.getDefault())?.contains("SSL") == false) {
                        protocols.add(protocol)
                    }
                }
                protocolsCompat = protocols.toArray(arrayOfNulls<String>(protocols.size))
                /* set up reasonable cipher suites */
                //if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
                // choose known secure cipher suites
                val allowedCiphers = listOf( // TLS 1.2
                    "TLS_RSA_WITH_AES_256_GCM_SHA384",
                    "TLS_RSA_WITH_AES_128_GCM_SHA256",
                    "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
                    "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
                    "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
                    "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
                    "TLS_ECHDE_RSA_WITH_AES_128_GCM_SHA256",  // maximum interoperability
                    "TLS_RSA_WITH_3DES_EDE_CBC_SHA",
                    "TLS_RSA_WITH_AES_128_CBC_SHA",  // additionally
                    "TLS_RSA_WITH_AES_256_CBC_SHA",
                    "TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA",
                    "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
                    "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA",
                    "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA"
                )
                val availableCiphers = listOf<String>(*socket.supportedCipherSuites)
                // take all allowed ciphers that are available and put them into preferredCiphers
                // take all allowed ciphers that are available and put them into preferredCiphers
                val preferredCiphers = HashSet(allowedCiphers)
                preferredCiphers.retainAll(availableCiphers.toSet())

                /* For maximum security, preferredCiphers should *replace* enabled ciphers (thus disabling
                 * ciphers which are enabled by default, but have become unsecure), but I guess for
                 * the security level of DAVdroid and maximum compatibility, disabling of insecure
                 * ciphers should be a server-side task */
                // add preferred ciphers to enabled ciphers
                preferredCiphers.addAll(HashSet(listOf(*socket.enabledCipherSuites)))
                cipherSuitesCompat = preferredCiphers.toTypedArray()
            }

            val sslContext = SSLContext.getInstance("TLS")
            sslContext.init(null, if (x509Tm != null) arrayOf(x509Tm) else null, null)
            defaultFactory = sslContext.socketFactory

        }catch (e: IOException){
            throw RuntimeException(e)
        }catch (e: GeneralSecurityException){
            throw AssertionError()
        }catch (e: Exception){
            e.printStackTrace()
        }
    }

    override fun createSocket(s: Socket?, host: String?, port: Int, autoClose: Boolean): Socket {
        val ssl = defaultFactory!!.createSocket(s, host, port, autoClose)
        if (ssl is SSLSocket) upgradeTLS(ssl)
        return ssl
    }

    override fun createSocket(host: String?, port: Int): Socket {
        val ssl = defaultFactory!!.createSocket(host, port)
        if (ssl is SSLSocket) upgradeTLS(ssl)
        return ssl
    }

    override fun createSocket(
        host: String?,
        port: Int,
        localHost: InetAddress?,
        localPort: Int
    ): Socket {
        val ssl = defaultFactory!!.createSocket(host, port, localHost, localPort)
        if (ssl is SSLSocket) upgradeTLS(ssl)
        return ssl
    }

    override fun createSocket(host: InetAddress?, port: Int): Socket {
        val ssl = defaultFactory!!.createSocket(host, port)
        if (ssl is SSLSocket) upgradeTLS(ssl)
        return ssl
    }

    override fun createSocket(
        address: InetAddress?,
        port: Int,
        localAddress: InetAddress?,
        localPort: Int
    ): Socket {
        val ssl = defaultFactory!!.createSocket(address, port, localAddress, localPort)
        if (ssl is SSLSocket) upgradeTLS(ssl)
        return ssl
    }

    private fun upgradeTLS(ssl: SSLSocket){
        // Android 5.0+ (API level21) provides reasonable default settings
        // but it still allows SSLv3
        // https://developer.android.com/about/versions/android-5.0-changes.html#ssl

        // Android 5.0+ (API level21) provides reasonable default settings
        // but it still allows SSLv3
        // https://developer.android.com/about/versions/android-5.0-changes.html#ssl
        if (protocolsCompat != null) {
            ssl.enabledProtocols = protocolsCompat
        }
        if ( /*Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP && */cipherSuitesCompat != null) {
            ssl.enabledCipherSuites = cipherSuitesCompat
        }
    }

    override fun getDefaultCipherSuites(): Array<String> = cipherSuitesCompat?: arrayOf()

    override fun getSupportedCipherSuites(): Array<String> = cipherSuitesCompat?: arrayOf()
}